package starter.delayclient;


import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.swagger.models.auth.In;
import jline.internal.Log;
import lombok.extern.slf4j.Slf4j;
import org.jackdking.delay.domainv1.client.console.ConsoleCommandManager;
import org.jackdking.delay.domainv1.client.console.LoginConsoleCommand;
import org.jackdking.delay.domainv1.client.handler.*;
import org.jackdking.delay.domainv1.infrastructure.codec.PacketDecoder;
import org.jackdking.delay.domainv1.infrastructure.codec.PacketEncoder;
import org.jackdking.delay.domainv1.infrastructure.codec.Spliter;
import org.jackdking.delay.domainv1.infrastructure.handler.IMIdleStateHandler;
import org.jackdking.delay.domainv1.infrastructure.util.SessionUtil;
import org.springframework.context.Lifecycle;

import java.util.Date;
import java.util.Scanner;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;

@Slf4j
public class NettyClient implements Lifecycle {

    private static final int MAX_RETRY = 5;
    private final String host;
    private final int port;
    private Channel channel;
    Bootstrap bootstrap;
    EventLoopGroup group;
    AtomicBoolean status = new AtomicBoolean(Boolean.FALSE);
    CompletableFuture<Boolean> loginFuture = new CompletableFuture<>();

    //连接服务端的端口号地址和端口号
    //host、port可以通过不同的客户端设置
    public NettyClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public Boolean isLoggedIn() {
        try {
            return loginFuture.get();
        } catch (InterruptedException e) {
            log.error("login fail , {}", e);
        } catch (ExecutionException e) {
            log.error("login fail , {}", e);
        }
        return false;
    }

    public void start() {
        group = new NioEventLoopGroup();

        bootstrap = new Bootstrap();
        bootstrap
                .group(group)
                .channel(NioSocketChannel.class)// 使用NioSocketChannel来作为连接用的channel类
                .option(ChannelOption.SO_BACKLOG, 1024)
                .option(ChannelOption.SO_KEEPALIVE,true)
                .option(ChannelOption.TCP_NODELAY, true)
                .handler(new ChannelInitializer<SocketChannel>() { // 绑定连接初始化器
                    @Override
                    public void initChannel(SocketChannel ch) throws Exception {
                        log.info("start connect server...");

                        // 空闲检测
                        ch.pipeline().addLast(new IMIdleStateHandler());

                        ch.pipeline().addLast(new Spliter());
                        ch.pipeline().addLast(new PacketDecoder());

                        ch.pipeline().addLast(new PacketEncoder());
                        ch.pipeline().addLast(new LoginResponseHandler(loginFuture));

                        // 心跳定时器,定期发送心跳检测信号给服务端，这个只作为一个最后的handler使用。
                        ch.pipeline().addLast(new HeartBeatTimerHandler());

                    }
                });

        connect(bootstrap, host, port, MAX_RETRY);
        status.compareAndSet(Boolean.FALSE, Boolean.TRUE);
        loggedInCheck();
    }

    private void loggedInCheck() {
        LoggedInCheck.build(this).loggedInCheck();
    }

    @Override
    public void stop() {
        if (isRunning()){
            group.shutdown();
            status.compareAndSet(Boolean.TRUE, Boolean.FALSE);
        }
    }

    @Override
    public boolean isRunning() {
        return status.get();
    }

    private void connect(Bootstrap bootstrap, String host, int port, int retry) {

        //发起异步连接请求，绑定连接端口和host信息
        try {

            ChannelFuture resultFuture = bootstrap.connect(host, port).sync();

            resultFuture.addListener(future -> {
                if (future.isSuccess()) {
                    log.info(new Date() + ": 连接成功，启动控制台线程……");
                    Channel chan = ((ChannelFuture) future).channel();
//                    startConsoleThread(chan);
                } else if (retry == 0) {
                    log.info("重试次数已用完，放弃连接！");
                } else {
                    // 第几次重连
                    int order = (MAX_RETRY - retry) + 1;
                    // 本次重连的间隔
                    int delay = 1 << order;
                    log.info(new Date() + ": 连接失败，第" + order + "次重连……");
                    bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retry - 1), delay, TimeUnit
                            .SECONDS);
                }
            });
            channel = resultFuture.channel();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static void startConsoleThread(Channel channel) {
        ConsoleCommandManager consoleCommandManager = new ConsoleCommandManager();
        LoginConsoleCommand loginConsoleCommand = new LoginConsoleCommand();
        Scanner scanner = new Scanner(System.in);

        new Thread(() -> {
            while (!Thread.interrupted()) {
                if (!SessionUtil.hasLogin(channel)) {
                    //not login channel, enter username and password to login
                    loginConsoleCommand.exec(scanner, channel);
                } else {
                    //has login channel, enter other command to do business
                    consoleCommandManager.exec(scanner, channel);
                }
            }
        }).start();
    }

    public Channel getChannel() {
        return channel;
    }


    static final class LoggedInCheck {

        int maxLoggedInCheckTimes = 3;
        Integer checkedLoggedInTimes = 1;
        NettyClient nettyClient;
        public static LoggedInCheck build(NettyClient nettyClient) {
            LoggedInCheck loggedInCheck = new LoggedInCheck(nettyClient);
            return loggedInCheck.self();
        }

        public LoggedInCheck(NettyClient nettyClient) {
            this.nettyClient = nettyClient;
        }

        private LoggedInCheck self() {
            return LoggedInCheck.this;
        }

        public void loggedInCheck() {
            Executors.newSingleThreadScheduledExecutor().schedule(()->{
                if (!nettyClient.isLoggedIn()) {
                    if (checkedLoggedInTimes <= maxLoggedInCheckTimes) {
                        checkedLoggedInTimes ++;
                        loggedInCheck();
                    }
                    nettyClient.stop();
                }
            }, 2, TimeUnit.SECONDS);

        }
    }

}